﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;

using Microsoft.Practices.Unity;

using ERPStore.Extensions;

namespace ERPStore.Cart.Repositories
{
	public class SqlCartRepository : CloudSoft.Repositories.SqlRepository<CartDbContext>
			, ICartRepository
	{
		private CloudSoft.Repositories.IDbContextFactory<CartDbContext> m_DbContextFactory;

        public SqlCartRepository(
			ERPStore.Services.ICacheService cacheService
			, ERPStore.Logging.ILogger logger
			, CloudSoft.Repositories.IDbContextFactory<CartDbContext> dbContextFactory
			, ERPStore.Services.ICatalogService catalogService)
		{
			this.CacheService = cacheService;
			this.Logger = logger;
			this.m_DbContextFactory = dbContextFactory;
			this.CatalogService = catalogService;
		}

		protected ERPStore.Services.ICacheService CacheService { get; set; }
		protected ERPStore.Logging.ILogger Logger { get; set; }
		protected ERPStore.Services.ICatalogService CatalogService { get; set; }

		public override CartDbContext GetDbContext()
		{
			return m_DbContextFactory.GetDbContext();
		}

		public IQueryable<Datas.Cart> GetCartList()
		{
			var db = GetDbContext();
			var list = from cart in db.Carts.Include(c => c.Items)
					   where cart.CartStateId != (int)ERPStore.Models.CartState.CanceledByUser
					   select cart;

			return list;
		}

		public void Save(ERPStore.Models.OrderCart cart)
		{
            using (var db = GetDbContext())
			{
				var cartData = db.Carts.FirstOrDefault(i => i.Code == cart.Code);
				if (cartData == null)
				{
					cartData = new Datas.Cart();
					cartData.Code = cart.Code;
					cartData.CreationDate = DateTime.Now;
					cartData.CartStateId = (int)ERPStore.Models.CartState.CurrentOrder;

					db.Carts.Add(cartData);
				}
				else if ((int) cart.State != 0)
				{
					cartData.CartStateId = (int)cart.State;
				}
				cartData.CustomerDocumentReference = cart.CustomerDocumentReference;
				cartData.CustomerPersonId = cart.CustomerId;
				cartData.LastUpdate = DateTime.Now;
				cartData.Message = cart.Message;
				cartData.VisitorId = cart.VisitorId;
				if (cart.LastPage != null 
					&& cart.LastPage.Length < 256)
				{
					cartData.LastPage = cart.LastPage;
				}
				cartData.LeadSourceName = cart.LeadSourceName;
				cartData.LeadSourceId = cart.LeadSourceId;

				var orderCart = cart as ERPStore.Models.OrderCart;
				if (orderCart.DeliveryAddress != null)
				{
					cartData.DeliveryAddressId = orderCart.DeliveryAddress.Id;
				}
				cartData.IsPresent = orderCart.IsPresent;
				cartData.PaymentModeName = orderCart.PaymentModeName;
				cartData.ShippingFeeAmount = Convert.ToInt64(orderCart.ShippingFee.Amount * 1000000);
				cartData.ShippingFeeTaxRate = Convert.ToInt32(orderCart.ShippingFee.TaxRate * 10000);
				cartData.AllowPartialDelivery = orderCart.AllowPartialDelivery;
				cartData.FromEntityId = orderCart.FromEntityId;
				cartData.FromMetaEntityId = orderCart.FromMetaEntityId;
				cartData.ShippingFeeLocked = orderCart.ShippingFeeLocked;
				if (orderCart.Conveyor != null)
				{
					cartData.ConveyorId = orderCart.Conveyor.Id;
				}
				cartData.AcceptTermsAndConditions = orderCart.AcceptCondition;
				cartData.MessageForConveyor = orderCart.MessageForConveyor;

				if (cart.ConvertedEntityId.HasValue)
				{
					cartData.ConvertedEntityId = cart.ConvertedEntityId.Value;
					cartData.ConversionDate = DateTime.Now;
					cartData.CartStateId = (int)ERPStore.Models.CartState.ConvertedToOrder;
				}
                else
                {
                    cartData.ConvertedEntityId = cart.ConvertedEntityId;
                    cartData.ConversionDate = cart.ConversionDate;
                }

				var retryCount = 0;
retry:
				try
				{
					db.SaveChanges();
					cart.Id = cartData.Id;
				}
				catch (System.Data.Entity.Infrastructure.DbUpdateConcurrencyException dbuce)
				{
					((System.Data.Entity.Infrastructure.IObjectContextAdapter)db).ObjectContext.Refresh(System.Data.Entity.Core.Objects.RefreshMode.ClientWins, cartData);
					Logger.Warn(dbuce.Message);
					retryCount++;
					if (retryCount < 3)
					{
						System.Threading.Thread.Sleep(100);
						goto retry;
					}
				}
				catch (System.Data.DBConcurrencyException oce)
				{
					((System.Data.Entity.Infrastructure.IObjectContextAdapter)db).ObjectContext.Refresh(System.Data.Entity.Core.Objects.RefreshMode.ClientWins, cartData);
					Logger.Warn(oce.Message);
					retryCount++;
					if (retryCount < 3)
					{
						System.Threading.Thread.Sleep(100);
						goto retry;
					}
				}
				catch (System.Data.Entity.Validation.DbEntityValidationException ex)
				{
					foreach (var item in ex.EntityValidationErrors)
					{
						foreach (var error in item.ValidationErrors)
						{
							ex.Data.Add("SqlCartRepository.Save." + error.PropertyName, error.ErrorMessage);
						}
					}
					throw;
				}
				catch
				{
					throw;
				}

				var cartItemList = db.CartItems.Where(i => i.CartId == cartData.Id && !i.IsArchived);

				foreach (var cartItem in cart.Items)
				{
					var cartItemData = cartItemList.SingleOrDefault(i => i.Id == cartItem.Id);
					if (cartItemData == null)
					{
						cartItemData = new Datas.CartItem();
						db.CartItems.Add(cartItemData);
						cartItemData.CartId = cartData.Id;
						cartItemData.ProductId = cartItem.Product.Id;
						cartItemData.CreationDate = cartItem.CreationDate;
					}
					cartItemData.LastUpdate = cartItem.LastUpdate;
					cartItemData.Quantity = cartItem.Quantity;
					cartItemData.SalePrice = Convert.ToInt64(cartItem.SalePrice.Value * 1000000);
					cartItemData.Discount = Convert.ToInt32(cartItem.Discount * 10000);
					cartItemData.ShippingTypeId = (int)cartItem.ShippingType;
					cartItemData.AllowPartialDelivery = cartItem.AllowPartialDelivery;
					cartItemData.AddFrom = cartItem.AddFrom;
					cartItemData.OfferName = cartItem.OfferName;
					cartItemData.InventoryTimeStateId = cartItem.InventoryTimeStateId;

					try
					{
						db.SaveChanges();
					}
					catch (System.Data.Entity.Validation.DbEntityValidationException ex)
					{
						foreach (var item in ex.EntityValidationErrors)
						{
							foreach (var error in item.ValidationErrors)
							{
								ex.Data.Add("SqlCartRepository.Save." + error.PropertyName, error.ErrorMessage);
							}
						}
						throw;
					}
					catch
					{
						throw;
					}
					cartItem.Id = cartItemData.Id;
				}

				// Logic remove items
				var existingDataRows = cartItemList.ToList().Select(i => i.Id);
				var existingRows = cart.Items.Where(i => i.Id != 0).Select(i => i.Id);
				var itemToDelete = existingDataRows.Except(existingRows);

				foreach (var item in itemToDelete)
				{
					var cartItemData = cartItemList.Where(i => !i.IsArchived).First(i => i.Id == item);
					cartItemData.IsArchived = true;
				}

				retryCount = 0;
retry3:
				try
				{
					db.SaveChanges();
				}
				catch (System.Data.DBConcurrencyException oce)
				{
					((System.Data.Entity.Infrastructure.IObjectContextAdapter)db).ObjectContext.Refresh(System.Data.Entity.Core.Objects.RefreshMode.ClientWins, cartData);
					Logger.Warn(oce.Message);
					retryCount++;
					if (retryCount < 3)
					{
						System.Threading.Thread.Sleep(200);
						goto retry3;
					}
				}
				catch (System.Data.Entity.Validation.DbEntityValidationException ex)
				{
					foreach (var item in ex.EntityValidationErrors)
					{
						foreach (var error in item.ValidationErrors)
						{
							ex.Data.Add("SqlCartRepository.Save." + error.PropertyName, error.ErrorMessage);
						}
					}
					throw;
				}
				catch
				{
					throw;
				}

			}
		}

		public void Remove(string cartId)
		{
			using (var db = GetDbContext())
			{
				var cartData = db.Carts.FirstOrDefault(i => i.Code == cartId);
				if (cartData == null)
				{
					return;
				}
				cartData.CartStateId = (int)ERPStore.Models.CartState.CanceledByUser;
				db.SaveChanges();
			}
		}

		public IList<ERPStore.Models.CartItem> GetLastCartItem(int itemCount)
		{
			var result = new List<ERPStore.Models.CartItem>();
            using (var db = GetDbContext())
			{
				var list = (from item in db.CartItems
							orderby item.Id descending
							select item).Take(itemCount * 2);

				foreach (var item in list)
				{
                    var product = CatalogService.GetProductById(item.ProductId);
					if (product == null
						|| !product.IsSellable)
					{
						continue;
					}
                    var cartItem = item.ToCartItemModel(product);
					if (cartItem != null
						&& !result.Any(i => i.Product.Id == item.ProductId))
					{
						result.Add(cartItem);
					}
				}
			}
			return result.Take(itemCount).ToList();
		}
	}
}
